#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <string.h>


// 1.实现单线程的TCP服务端
// 2.在单线程TCP服务端的基础上，添加epoll代码，让epoll监控侦听套接字和新连接的套接字
//   侦听套接字只有一个，新连接的套接字有多个
//   侦听套接字(读事件)：一旦读事件就绪，表示当前有新连接，三次握手建立连接了，调用accept函数处理读事件
//   新连接的套接字：一旦读事件就绪了，表示当前客户端给服务端发送消息了，调用recv函数处理读事件

int main()
{
    //建立侦听套接字，只有服务端有侦听套接字
    int listen_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(listen_sockfd < 0)
    {
        perror("socket:");
        return 0;
    }

    //绑定地址信息
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(45678);
    addr.sin_addr.s_addr = inet_addr("0.0.0.0");

    int ret = bind(listen_sockfd, (struct sockaddr*)&addr, sizeof(addr));
    {
        if(ret < 0)
        {
            perror("bind:");
            return 0;
        }
    }

    //监听
    ret = listen(listen_sockfd, 5);
    if(ret < 0)
    {
        perror("listen:");
        return 0;
    }

    // 1.创建epoll操作句柄
    // 2.添加侦听套接字，让epoll监控
    // 3.监控到侦听套接字的读事件之后，调用accept函数处理读事件(接受新连接)
    //      区分到底是新连接套接字还是侦听套接字，分别处理
    //      对于新连接的套接字还需要添加到epoll当中进行监控
    //   如果是新连接套接字的读事件产生，则接收数据
    
    int epfd = epoll_create(6);
    if(epfd < 0)
    {
        perror("epoll_create:");
        return 0;
    }

    struct epoll_event ee;
    ee.events = EPOLLIN;
    ee.data.fd = listen_sockfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sockfd, &ee); //将listen_sockfd加入到事件结构当中，让epoll开始监控，如果监控到了，就会把这个产生的事件结构拷贝到双向链表当中
    

    while(1)
    {
        struct epoll_event arr[10];
        //程序员可以调用epoll_wait函数，从双向链表当中拿到这个产生的事件
        int ret = epoll_wait(epfd, arr, 10, -1); //返回的是就绪的事件结构的个数，将返回的事件结构个数放入到arr数组当中
        if(ret < 0) //监控失败
        {
            perror("epoll_wait:");
            continue;
        }
        else if(ret == 0) //超时
        {
            continue;
        }

        //能执行到这里就说明epoll监控到的文件描述符，有对应的就绪事件产生
        //对上面的就绪事件开始遍历
        for(int i = 0; i < ret;  i++) //ret是个数,说明arr数组有ret个元素，遍历arr数组
        {
            if(arr[i].data.fd == listen_sockfd) //如果是侦听套接字
            {
                int newsockfd = accept(listen_sockfd, NULL, NULL);//对端的地址信息结构和长度不需要先可以设为NULL
                if(newsockfd < 0) //接收失败
                {
                    perror("accept:");
                    continue;
                }
                //接收成功
                //将接收成功的新套接字放入到epoll当中进行监控
                struct epoll_event new_ee;
                new_ee.events = EPOLLIN;
                new_ee.data.fd = newsockfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, newsockfd, &new_ee);
            }
            else //arr[i].data.fd == newsockfd; 
            {
                int newsockfd = arr[i].data.fd;
                char buf[1024] = {0};
                ssize_t recv_size = recv(newsockfd, buf, sizeof(buf), 0);//0表示阻塞接收
                if(recv_size < 0)
                {
                    perror("recv_size:");
                    continue;
                }
                else if(recv_size == 0)
                {
                    printf("peer shut down connect\n");
                    //对端关闭连接，自己在epoll当中要把相应的事件结构删除
                    epoll_ctl(epfd, EPOLL_CTL_DEL, newsockfd, NULL);
                    //自己关闭连接
                    close(newsockfd);
                    continue;
                }
                
                printf("buf:%s\n", buf);

                //发送
                memset(buf, '\0', sizeof(buf)); //初始化
                strcpy(buf, "hello, i am server~");
                send(newsockfd, buf, strlen(buf), 0);
            }
        }

    }

    epoll_ctl(epfd, EPOLL_CTL_DEL, listen_sockfd, NULL);
    close(listen_sockfd);
    return 0;
}
